package com.song.proxy;


import com.song.client.NettyClient;
import com.song.client.RpcClient;
import com.song.transport.RPCRequest;
import com.song.transport.RPCResponse;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

/**
 * 采用JDK的动态代理  代理类是需要实现InvocationHandler接口的
 * 通过动态代理的方式生成实例，
 * 并且调用方法时生成需要的RpcRequest对象并且发送给服务端
 */

/**
 * 使用 JDK 动态代理类基本步骤：
 * 1、编写需要被代理的类和接口
 * 2、编写代理类，需要实现 InvocationHandler 接口，重写 invoke() 方法；
 * 3、使用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)动态创建代理类对象，通过代理类对象调用业务方法。
 */
@Slf4j(topic = "c.RPCClientProxy")
public class RPCClientProxy implements InvocationHandler {//实现代理者的接口为InvocationHandler

//    private String host;
//    private int port;
//    public RPCClientProxy(String host, int port) {
//        this.host = host;
//        this.port = port;
//    }

    private RpcClient client;//接口

    public RPCClientProxy(RpcClient client) {
        this.client = client;
    }


    //对传入的类对象 进行动态代理 只能通过接口
    //传递host和port来指明服务端的位置。并且使用 getProxy()方法来生成代理对象
    //动态创建代理类对象，通过代理类对象 调用业务方法。
    @SuppressWarnings("unchecked")
    public <T> T getProxy(Class<T> clazz) {// JDK的动态代理只能通过接口实现 而CGLIB不需要
        //jdk的动态代理实现
        ClassLoader loader = clazz.getClassLoader();//类加载器    获得当前类的ClassLoader 决定用哪个类加载器去加载代理对象
        Class<?>[] interfaces = new Class<?>[]{clazz};//接口    动态代理类需要实现的 接口

        //第三个参数 InvocationHandler h  实现这个接口InvocationHandle，创建代理对象，写增强方法
        return (T) Proxy.newProxyInstance(loader, interfaces, this);//执行Proxy类的实现invoke()方法
    }

    //proxy：代理对象  在invoke一般不会使用      method： 调用的方法      args：调用方法传入的参数
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//        反射获取传入 类对象 的方法 和 属性
        log.info("客户端调用了：{} # {}", method.getDeclaringClass().getName(), method.getName());

        RPCRequest rpcRequest = RPCRequest.builder()
                .requestId(UUID.randomUUID().toString()) //分布式生成独一无二的请求UUID
                .interfaceName(method.getDeclaringClass().getName())//通用接口名称    com.song.api.HelloService
                .methodName(method.getName())//反射通用接口的 方法名称   hello
                .parameters(args)//反射通用接口的方法获取的参数   传入的参数HelloObject(id=13, message=This client 1)
                .paramTypes(method.getParameterTypes())//参数类型   class com.song.api.HelloObject
                .build();

        RPCResponse rpcResponse = null;

        if(client instanceof NettyClient) {
            try{
                /**
                 * CompletableFuture  提供了非常强大的Future的扩展功能  Future的get方法是阻塞主线程等待（浪费CPU资源），CompletableFuture实现了异步，未阻塞主线程
                 * CompletableFuture为Future的扩展类，实现了Future, CompletionStage两个接口，实现了异步计算
                 * 引入CompletableFuture 对业务流程进行编排，降低依赖之间的阻塞  https://tech.meituan.com/2022/05/12/principles-and-practices-of-completablefuture.html
                 */
                CompletableFuture<RPCResponse> completableFuture = (CompletableFuture<RPCResponse>) client.sendRequest(rpcRequest);//此sendRequest方法中的ChannelProvider类的ChannelHandle的 NettyClientHandler类 实现了 complete得到返回RPCResponse

                //等待任务执行完成并获取运算结果 获取异步计算的结果，如果没有完成会一直阻塞等待， 直到client.sendRequest(rpcRequest)线程完成，获得    RPCResponse类
                //异步阻塞等待获取，get（）和join()的 区别在于join 返回的计算结果或者抛出一个unchecked异常，而get抛出具体异常
                rpcResponse = completableFuture.get();
            } catch (Exception e) {
                log.error("方法调用请求发送失败。。。", e);
                return null;
            }
        }
        return rpcResponse.getData();//是根据method的返回值决定的
    }
}
